#!/bin/bash -n

function shproc() { # shproc [option] ID = pipe
    
    # option -i: set initial input file for coproc
    # option -o: set initial output file for coproc
    # option -e: set initial error file for coproc

    local e= i= o=;
    
    (( $# > 0 )) || return;
    
    while [[ ${1:0:1} = - ]]; do
        [[ $1 = -e ]] && { e=$2; shift 2; continue; };
        [[ $1 = -i ]] && { i=$2; shift 2; continue; };
        [[ $1 = -o ]] && { o=$2; shift 2; continue; };
    done;
    
    declare ID=$1 cleanup='
        coproc_open_descriptors
        coproc_spawn
    ';
    
    declare -gA $ID[input]="${i}" $ID[output]="${o}" error="${e}" || return;
       
    local in=$ID[input] out=$ID[output];
    
    coproc_clean() {
        local -i r=$?
        unset -f $cleanup coproc_clean;
        return $r;
    }
    
    coproc_open_descriptors() {
      local -i value ulimit=`ulimit -n`;
        while value=$(( (RANDOM % (ulimit-15))+10 )); do
            [[ -e /dev/fd/$value ]] || {
                break;
            }
        done
        local slot; for slot in $ID[input] $ID[output]; do
            [[ -n ${!slot} ]] && continue;
            while [[ -e /dev/fd/$value ]]; do let value++; done
            eval "exec $value<> >(:);";
            declare -g $slot=/dev/fd/$value;
        done
    }
    
    coproc_spawn() {
        local error=$ID[error]; error=${!error:=/dev/fd/26};
        exec 26>&2; exec 2>/tmp/proc.$$.cs;
        eval $(echo '{ "$@" & }' '0< '${!in} '1> '${!out});
        exec 2>&26 26>&-; local -a info; 
        IFS='[] ' read -a info </tmp/proc.$$.cs; rm /tmp/proc.$$.cs;
        declare -g $ID[0]=${info[2]} $ID[cmd]="${*:1:1}" $ID[args]="${*:2}" \
        $ID[jid]=${info[1]};  
    }
 
    shift 2;
    
    coproc_open_descriptors;
    
    coproc_spawn "$@";
        
    coproc_clean;
    
cat > /tmp/$$.src <<EOF

function $ID.close() { 
    # can't close files that aren't attached to this process's descriptors
    [[ \${$ID[input]} =~ ^/dev/fd/ ]] && eval "exec \${$ID[input]##*/}<&-"
    [[ \${$ID[output]} =~ ^/dev/fd/ ]] && eval "exec \${$ID[output]##*/}<&-";
}

function $ID.read() { read "\$@" < \${$ID[output]}; }

function $ID.write() {
    (( \$# > 0 )) && {
        echo "\$@" > \${$ID[input]}
    } || {
        cat < /dev/fd/0 > \${$ID[input]};
    }
} # use cat for binary input

function $ID.signal() { kill "\$@" \${$ID}; }
function $ID.pause() { kill -s SIGSTOP \${$ID}; }
function $ID.continue() { kill -s SIGCONT \${$ID}; }

function $ID.die() {

    { kill \${$ID}; $ID.close;
        unset -f $(echo \
        ${ID}.{close,read,write,signal,pause,continue,die});
        unset $ID;
    }   2>/dev/null;
    
    true;
    
}   
EOF

source /tmp/$$.src; rm /tmp/$$.src;

}
